//
// Created by hjie on 23-8-6.
//

#include "message_process.h"
#include "data/turn_data_manager.h"
#include "data/nonce_data.h"
#include "listen/stun_server_manager.h"
#include "listen/stun_udp_server.h"
#include "message_attr.h"
#include "stun_message.h"
#include "turn_server.h"
#include <arpa/inet.h>
#include <cstdint>
#include <cstdio>
#include <functional>
#include <netinet/in.h>
#include <strings.h>
#include <sys/socket.h>

static std::string realm_content = "12345";

MessageProcess::MessageProcess(TurnServer *server)
{
    m_stun_server = server;
    m_turn_data_manager   = std::make_shared<TurnDataManager>(this);
    m_stun_server_manager = std::make_shared<StunServerManager>(this, server->m_config->GetPublicIp());
    m_loop_event = m_stun_server->m_loop_event;
    // register message function map
    RegisterFunction();
}

void MessageProcess::CheckUdpServerExpire()
{

    m_stun_server_manager->CheckRelayServerExpire();
    // check data
    m_turn_data_manager->DataTimer();
}

void MessageProcess::RegisterFunction()
{
    m_stun_message_func_map[EmBindRequest]      = std::bind(&MessageProcess::ProcessRequest, this, std::placeholders::_1, std::placeholders::_2);
    m_stun_message_func_map[EmAllocateRequest]  = std::bind(&MessageProcess::ProcessAllocate, this, std::placeholders::_1, std::placeholders::_2);
    m_stun_message_func_map[EmRefreshRequest]   = std::bind(&MessageProcess::ProcessRefreshRequest, this, std::placeholders::_1, std::placeholders::_2);
    m_stun_message_func_map[EmSendIndication]   = std::bind(&MessageProcess::ProcessSendIndication, this, std::placeholders::_1, std::placeholders::_2);
    m_stun_message_func_map[EmCreatePermission] = std::bind(&MessageProcess::ProcessCreatePermissionReq, this, std::placeholders::_1, std::placeholders::_2);
}

void MessageProcess::ProcessCommand(StunMessage &message, std::vector<StunMessage> &stun_message_vec)
{
    auto iter = m_stun_message_func_map.find(message.GetMsgType());
    if (iter != m_stun_message_func_map.end())
    {
        iter->second(message, stun_message_vec);
    }
}

void MessageProcess::ProcessRequest(StunMessage &message, std::vector<StunMessage> & stun_message_vec)
{
    StunMessage response;
    response.SetMsgType(EmBindResponse);
    response.m_transaction_id = message.m_transaction_id;
    response.m_magic_cookie   = message.m_magic_cookie;

    auto attr_data = message.GetAttrById(EmXorMappedAddress);
    if (attr_data)
    {
        response.AddStunAttr(attr_data);
    }
    stun_message_vec.push_back(response);
}


void MessageProcess::ProcessAllocate(StunMessage &message, std::vector<StunMessage> & stun_message_vec)
{
    (void)m_stun_server;

    auto nonce_attr_data = message.GetAttrById(EmNONCE);

    if (nonce_attr_data)
    {
        auto nonce_attr = (AttrNonce*)(nonce_attr_data.get());


        std::string nonce_id = nonce_attr->GetNonceData();

        if (m_turn_data_manager->m_nonce_manager->CheckNonceData(nonce_id))
        {
            printf("find the nonce id : %s.\n", nonce_id.c_str());
            StunMessage response;
            response.SetMsgType(EmAllocateSuccessResponse);
            response.m_transaction_id = message.m_transaction_id;
            response.m_magic_cookie   = message.m_magic_cookie;

            auto xor_addr_attr = message.GetAttrById(EmXorMappedAddress);
            if (xor_addr_attr)
            {
                response.AddStunAttr(xor_addr_attr);
            }
            AttrLifeTime * left_time = new AttrLifeTime();
            response.AddStunAttr(left_time);

            auto server = m_stun_server_manager->CreateUdpServer(nonce_id);
            if (xor_addr_attr)
            {
                auto xor_addr = (AttrXorMappedAddress*)xor_addr_attr.get();
                server->SetAllocator(xor_addr);
            }
            auto relay_address = new AttrXorMappedAddress();
            relay_address->ParseAddrInfo(IPPROTO_IP, (struct sockaddr*)&server->m_server_sai, sizeof(server->m_server_sai));
            relay_address->SetAttrType(EmXorRelayedAddress);
            response.AddStunAttr(relay_address);

            stun_message_vec.push_back(response);
        }
    }
    else
    {
        AttrErrorCode * error_code = new AttrErrorCode();
        error_code->SetError(04, 1);

        AttrRealm * realm = new AttrRealm();
        realm->SetRealm(realm_content);

        AttrNonce * nonce = new AttrNonce();

        std::string content;
        m_turn_data_manager->m_nonce_manager->ProducerNonceData(true, content);
        nonce->SetNonce(content);

        printf("%s nonce data : %s.\n", __func__, content.c_str());

        StunMessage response;
        response.SetMsgType(EmAllocateErrorResponse);
        response.m_transaction_id = message.m_transaction_id;
        response.m_magic_cookie   = message.m_magic_cookie;
        response.AddStunAttr(error_code);
        response.AddStunAttr(realm);
        response.AddStunAttr(nonce);
        stun_message_vec.push_back(response);
    }
}

void MessageProcess::ProcessRefreshRequest(StunMessage &message, std::vector<StunMessage> &stun_message_vec)
{

    auto nonce_attr_data = message.GetAttrById(EmNONCE);
    if (nonce_attr_data)
    {
        auto nonce_attr = (AttrNonce*)(nonce_attr_data.get());
        std::string nonce_id = nonce_attr->GetNonceData();

        if (m_turn_data_manager->m_nonce_manager->CheckNonceData(nonce_id))
        {
            printf("refresh relay allocate nocne data : %s.\n", nonce_id.c_str());
            auto server = m_stun_server_manager->FindStunServerByNonceId(nonce_id);
            if (server)
            {
                server->m_refresh_time = time(nullptr);
                // check allocator sai information if change swap the sai data
                auto xor_peer_attr = message.GetAttrById(EmXorMappedAddress);
                if (xor_peer_attr)
                {
                    auto xor_peer = (AttrXorMappedAddress*)(xor_peer_attr.get());
                    printf("function[%s]ip : %s\tport : %u.\n", __func__, xor_peer->LocalIp().c_str(), xor_peer->LocalPort());
                    server->UpdateAllocatorInfo(xor_peer);
                }
            }
            m_turn_data_manager->m_nonce_manager->UpdateNonceData(nonce_id);

            StunMessage response;
            response.SetMsgType(EmRefreshRequestResponse);
            response.m_transaction_id = message.m_transaction_id;
            response.m_magic_cookie   = message.m_magic_cookie;

            AttrLifeTime * life_time = new AttrLifeTime();
            response.AddStunAttr(life_time);

            stun_message_vec.push_back(response);
        }
        else
        {
            // add a error attr
        }
    }
    else
    {
        // add a error attr
    }
}

void MessageProcess::ProcessCreatePermissionReq(StunMessage &message, std::vector<StunMessage> &stun_message_vec)
{
    auto xor_peer = message.GetAttrById(EmXorPeerAddress);
    if (!xor_peer)
    {
        printf("no found xor peer address.\n");
        return;
    }
    auto nonce_attr_data = message.GetAttrById(EmNONCE);
    if (nonce_attr_data)
    {
        auto nonce_attr = (AttrNonce*)(nonce_attr_data.get());
        std::string nonce_id = nonce_attr->GetNonceData();

        if (m_turn_data_manager->m_nonce_manager->CheckNonceData(nonce_id))
        {
            uint16_t udp_server_port = 0;
            if ((udp_server_port = m_stun_server_manager->GetServerPortByNonceId(nonce_id)) > 0)
            {
                auto udp_server_data = m_stun_server_manager->FindStunServerByPort(udp_server_port);
                if (udp_server_data)
                {
                    // process create permission request
                    auto xor_attr = (AttrXorMappedAddress*)xor_peer.get();
                    printf("ip : %s\tport : %d.\n", xor_attr->DecoderAddr().c_str(), xor_attr->DecoderPort());

                    StunMessage response;
                    response.SetMsgType(EmCreatePermissionResponse);
                    response.m_magic_cookie   = message.m_magic_cookie;
                    response.m_transaction_id = message.m_transaction_id;

                    udp_server_data->AddSubscriber(xor_attr);

                    stun_message_vec.push_back(response);
                }
                else
                {
                    printf("no found the port %d udp server.\n", udp_server_port);
                }
            }
            else
            {
                printf("no allocate port information.\n");
            }
        }
        else
        {
            printf("no found nonce data from nonce map.\n");
        }
    }
    else
    {
        printf("no found nonce attr msg.\n");
    }
}


void MessageProcess::ProcessSendIndication(StunMessage &message, std::vector<StunMessage> &stun_message_vec)
{
    // auto attr = message.GetMsgAttr();

    auto mapped_attr_data = message.GetAttrById(EmXorMappedAddress);
    if (mapped_attr_data)
    {
        auto address = (AttrXorMappedAddress*)mapped_attr_data.get();
        auto server = m_stun_server_manager->FindStunServerByAddress(address);
        if (!server)
        {
            printf("no found server by address.\n");
            return;
        }
        auto data_attr_data = message.GetAttrById(EmData);
        // auto peer_attr_data = message.GetAttrById(EmXorPeerAddress);
        if (data_attr_data)
        {
            // auto peer_addr = (AttrXorMappedAddress*)peer_attr_data.get();
            auto data      = (AttrData*)data_attr_data.get();
            // server->SendDataSubscriber(peer_addr, &*data->m_data_vec.begin(), data->m_data_vec.size());
            server->BroadcastDataToSubscriber(&*data->m_data_vec.begin(), data->m_data_vec.size());
        }
    }
    else
    {

    }
}

void MessageProcess::ProcessMsgFromUdpServer(StunUdpServer * server, StunMessage &message)
{
    UdpBufferPacket packet{};
    DataBuffer buff(m_stun_server->m_udp_server->GetSocketFd());
    packet.buffer = &buff;
    packet.sai     = (struct sockaddr*)&server->m_allocator_sai;
    packet.sai_len = sizeof(server->m_allocator_sai);

    WriteBuffer writer;
    message.ComposeStunPacket(writer);

    m_stun_server->m_udp_server->SendMsg(packet, writer.Data(), writer.Length());
}

